function findPos(obj) {
  var curleft = curtop = 0;
  if (obj.offsetParent) {
    do {
      curleft += obj.offsetLeft;
      curtop += obj.offsetTop;
    } while (obj = obj.offsetParent);
  }
  return [curleft,curtop];
}

function Binding(sources, bindingFunction)
{
    this.sources = sources;
    this.bindingFunction = bindingFunction;
}


// a QML property encapsulates a value that triggers a number of callbacks
// Only for internal use!!
function QMLProperty(item, value, binding)
{
    this.m_value = value;
    this.item = item;
    this.listeners = [];
    if (binding) {
      this.binding = binding;
    } 
}
QMLProperty.prototype.value = function() {
   return this.m_value;
}
QMLProperty.prototype.setValue = function( value ) {
   this.m_value = value;
   // notify all the listeners
   for(l in this.listeners) {
     this.listeners[l].updateBindedValue();
   }
   this.item.updateRender();
}
QMLProperty.prototype.updateBindedValue = function( ) {
    this.setValue(this.binding.bindingFunction.call(this.item));
}


// Base class of QML elements
function Item(description, parent)
{
    this.width = new QMLProperty(this, 0);
    this.height = new QMLProperty(this, 0);
    this.x = new QMLProperty(this, 0);
    this.y = new QMLProperty(this, 0);
    this.id = undefined;
    this.parent = parent;
    for(d in description) {
      v = description[d];
      switch(d) {
	case 'id':
	  this.id = v;
	  break;
	case 'children':
	  this.children = v;
	  break;
	default:
	  if ( typeof(v) == "function") {
	    this[d] = v;
	  } else if (v instanceof Binding) {
	    this[d] = new QMLProperty(this, undefined, v);
	  } else {
	    this[d] = new QMLProperty(this, v);
	  }
	  break;
      }
    }
}

Item.prototype.constructor = Item;
Item.prototype.doScoping = function()
{
    // add properties corresponding to child element ids so that they are in scope
    for(c in this.children) {
      child = this.children[c];
      if (!child.parent) { // add current parent to the child
	child.parent = this;
      }
      // If the parent has an id, add the parent element as a property
      if (this.id && !child[this.id]) {
	child[this.id] = this;
      }      
      id = child.id;
      if (id && !this[id]) {
	this[id] = child;
      }
      child.doScoping();
    }
    
}
Item.prototype.doBindings = function()
{
  console.log("DO BINDINGS FOR " + this.id);
  for (p in this) {
    if (this[p] instanceof QMLProperty && this[p].binding) {
      ss = this[p].binding.sources;
      for (s in ss) {
	str = eval(ss[s]);
	source = eval(str);
	if (source && source instanceof QMLProperty) {
	    console.log("BIND " + this + " to " + ss[s] + " " +  source);
	    source.listeners.push(this[p]);
	}
      }
    }
  }
}
Item.prototype.initBindings = function()
{
    for(c in this.children) {
      child = this.children[c];
      for(p in child) {
	prop = child[p];
	if (prop && prop.binding) {
	  prop.updateBindedValue();
	}
      }
    }
  
}

Item.prototype.updateRender = function()
{
   if (!this.element) return;
   this.element.style.marginTop = this.y.value(); 
   this.element.style.marginLeft = this.x.value(); 
   this.element.style.width = this.width.value();
   this.element.style.height = this.height.value();
}

Item.prototype.render = function(parentElement)
{
  // add named reference to parent and other elements
  this.doScoping();
  
  // Do bindings now that we have collected all the elements
  this.doBindings();
  
  this.initBindings();
  
  // create DOM elements
  this.element = document.createElement('div');
  if (parentElement) {
    this.element.style.position = 'absolute';
    parentElement.appendChild(this.element);
  } else document.body.appendChild(this.element);
  this.updateRender();
  for(child in this.children) {
     r = this.children[child].render(this.element);
  }
}


// Colored rectangle
function Rectangle(description)
{
    Item.call(this, description);
}
Rectangle.prototype = new Item;
Rectangle.prototype.constructor = Rectangle;
Rectangle.prototype.render = function(parent)
{
   Item.prototype.render.call(this, parent);
   if (this.color)
    this.element.style["background"] = this.color.value();
}

// Mouse active area
function MouseArea(description)
{
    Item.call(this, description);
    this.mouseX = new QMLProperty(this, 0);
    this.mouseY = new QMLProperty(this, 0);
}
MouseArea.prototype = new Item;
MouseArea.prototype.constructor = MouseArea;
MouseArea.prototype.render = function(parent)
{
   Item.prototype.render.call(this, parent);
   var thiz = this; // utilla la chiusura per non passare this=this.element al processClick, ma direttamente this
   this.element.addEventListener("click", function(e){thiz.processClick(e);}, false);
}
MouseArea.prototype.processClick = function(e) {
  this.pos = findPos(this.element);
  this.mouseX.setValue(e.clientX - this.pos[0]);
  this.mouseY.setValue(e.clientY - this.pos[1]);
  //console.log(this.mouseX.value() + " " + this.mouseY.value());
  if (this.onClicked) this.onClicked();
}

